package com.us.usws.common.resourcetree;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.iwsh.core.db.DBExecutor;
import net.iwsh.core.exception.NoSuchObjectException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.us.usws.common.resourcetree.model.AbstractTreeNode;
import com.us.usws.common.resourcetree.model.CheckBoxResourceTree;
import com.us.usws.model.flat.system.DOUser;

/**
 *
 * @author Wang Jianbo
 */
public class ResourceTreeLoader {

	private static Log log = LogFactory.getLog(ResourceTreeLoader.class);

	private static ResourceTreeLoader instance = new ResourceTreeLoader();

	private static Map<String, List<Object[]>> data = Collections.synchronizedMap(new HashMap<String, List<Object[]>>());

	/**
	 * Private constructor
	 */
	private ResourceTreeLoader() {
	}

	/**
	 * Get singleton class instance.
	 * @return singleton class instance.
	 */
	public static ResourceTreeLoader getInstance() {
		return instance;
	}

	/**
	 * Refresh method published
	 */
	public void refresh() {
		if (data != null) {
			data.clear();
		}
	}

	/**
	 * get trees
	 * @param provider
	 * @return
	 */
	public <T extends AbstractTreeNode<T>> List<T> getResourceTrees(ResourceTreeRawDataProvider<T> provider) {
		return getTrees(provider.getRootId(), provider);
	}

	/**
	 * get tree by parent id
	 * @param provider
	 * @return
	 * @throws NoSuchObjectException 
	 */
	public <T extends AbstractTreeNode<T>> T getResourceTreeByParentId(ResourceTreeRawDataProvider<T> provider)
			throws NoSuchObjectException {
		return getTreeByRootId(provider.getRootId(), provider);
	}

	/**
	 * get tree by user privileg
	 * @param user
	 * @param provider
	 * @return
	 * @throws NoSuchObjectException 
	 */
	public <T extends AbstractTreeNode<T>> T getResourceTreesByPrivilege(DOUser user, ResourceTreeRawDataProvider<T> provider)
			throws NoSuchObjectException {
		T original = getTreeByRootId(provider.getRootId(), provider);

		//filterPrivilege(original, user);
		return original;
	}

	/**
	 * Get a ResourceKey from the cached map, by key.
	 * @param user
	 * @param provider
	 * @param selectId
	 * @param <T>
	 * @return
	 * @throws NoSuchObjectException
	 */
	public <T extends AbstractTreeNode<T>> T getResourceTreesByPrivilege(DOUser user, ResourceTreeRawDataProvider<T> provider,
			String selectId) throws NoSuchObjectException {
		T original = getTreeByRootId(provider.getRootId(), provider);

		//filterPrivilege(original, user);
		//		AbstractTreeNode node = getNode(original, selectId);
		//		if (node != null) {
		//			expand(node);
		//		}
		return original;
	}

	/**
	 * get checkbox trees
	 * @param rootIds
	 * @param provider
	 * @param selectedObjIds
	 * @return
	 */
	public List<CheckBoxResourceTree> getCheckBoxResourceTrees(List<Long> rootIds,
			ResourceTreeRawDataProvider<CheckBoxResourceTree> provider, List<String> selectedObjIds) {
		DBExecutor dbe = new DBExecutor();
		List<CheckBoxResourceTree> trees = new ArrayList<CheckBoxResourceTree>();
		for (Long rId : rootIds) {
			List<Object[]> allNodes = data.get(provider.getKey() + rId);
			if (allNodes == null) {
				allNodes = dbe.list(provider.getSQL(), rId);
				data.put(provider.getKey() + rId, allNodes);
			}
			CheckBoxResourceTree parent = null;
			for (Object[] obj : allNodes) {
				CheckBoxResourceTree node = provider.processRawData(obj);
				setChecked(selectedObjIds, node);
				if (node.getParentId() == rId) {//according parent_id
					parent = node;
					trees.add(parent);
				} else {
					if (parent != null) {
						append(parent, node);
					}
				}
			}
		}
		return trees;
	}

	/***********************private method************************************/

	/**
	 * get trees by parent id
	 * @param rootId
	 * @param provider
	 * @return
	 * @throws NoSuchObjectException 
	 */
	private <T extends AbstractTreeNode<T>> T getTreeByRootId(long rootId, ResourceTreeRawDataProvider<T> provider)
			throws NoSuchObjectException {
		DBExecutor dbe = new DBExecutor();

		List<Object[]> allNodes = data.get(provider.getKey() + rootId);
		if (allNodes == null) {
			allNodes = dbe.list(provider.getSQL(), rootId);
			data.put(provider.getKey() + rootId, allNodes);
		}

		//find roots firstly
		List<T> maybeRoots = new ArrayList<T>();
		List<T> allTs = new ArrayList<T>();
		for (Object[] obj : allNodes) {
			T node = provider.processRawData(obj);
			allTs.add(node);
			//log.info("Now get one node via SQL: [" + node.getId() + "] : [" + node.getText() + "] -- parent = [" + node.getParentId()
			//		+ "].");
			if (node.getId() == rootId) {
				maybeRoots.add(node);
			}
		}

		//if empty
		if (maybeRoots.isEmpty()) {
			throw new NoSuchObjectException("Cannot get tree from root ID: " + rootId);
		}

		//if we should have an unique top root
		T rootNode = null;
		if (maybeRoots.size() > 1) {
			try {
				rootNode = provider.getNodeClass().newInstance();
				rootNode.setChildren(maybeRoots);
			} catch (Exception e) {
				log.error("Create class instance error.", e);
			}
		} else {
			rootNode = maybeRoots.get(0);
		}

		//fill nodes
		fillChildren(rootNode, allTs);

		return rootNode;
	}

	/**
	 * fill nodes
	 * @param node
	 */
	private <T extends AbstractTreeNode<T>> void fillChildren(T node, List<T> allTs) {
		for (T each : allTs) {
			if (each.getParentId() == node.getId()) {
				node.add(each);
			}
		}
		if (!node.isLeaf()) {
			for (T each : node.getChildren()) {
				fillChildren(each, allTs);
			}
		}
	}

	/**
	 * private method, get trees
	 * @param rootId
	 * @param provider
	 * @return
	 */
	private <T extends AbstractTreeNode<T>> List<T> getTrees(long rootId, ResourceTreeRawDataProvider<T> provider) {
		DBExecutor dbe = new DBExecutor();
		List<T> trees = new ArrayList<T>();
		List<Object[]> allNodes = data.get(provider.getKey());
		if (allNodes == null) {
			allNodes = dbe.list(provider.getSQL(), rootId);
			data.put(provider.getKey(), allNodes);
		}
		T parent = null;
		for (Object[] obj : allNodes) {
			T node = provider.processRawData(obj);
			if (node.getId() == rootId) {//according id
				parent = node;
				trees.add(parent);
			} else {
				if (parent != null) {
					append(parent, node);
				}
			}
		}
		return trees;
	}

	private <T extends AbstractTreeNode<T>> void append(T parent, T newNode) {
		if (parent.getId() == newNode.getParentId()) {
			newNode.setParentId(parent.getId());
			parent.add(newNode);
		} else {
			for (T temp : parent.getChildren()) {
				append(temp, newNode);
			}
		}
	}

	/**
	 * 
	 * @param node
	 */
	@Deprecated
	private void expand(AbstractTreeNode<?> node) {
		node.setExpanded(true);
		//expand(node.getParent());
	}

	private void setChecked(List<String> privilegeIds, CheckBoxResourceTree node) {
		if (privilegeIds != null && !privilegeIds.isEmpty()) {
			for (String temp : privilegeIds) {
				if (temp.equals(String.valueOf(node.getId()))) {
					node.setChecked(true);
					expand(node);
				}
			}
		}
	}
}
